/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.javascript;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Map;

/**
 * This class reflects non-Array Java objects into the JavaScript environment.  It
 * reflect fields directly, and uses NativeJavaMethod objects to reflect (possibly
 * overloaded) methods.<p>
 *
 * @author Mike Shaver
 * @see NativeJavaArray
 * @see NativeJavaPackage
 * @see NativeJavaClass
 */

public class NativeJavaObject implements Scriptable, Wrapper, Serializable
{
    static final long serialVersionUID = -6948590651130498591L;

    public NativeJavaObject() { }

    public NativeJavaObject(Scriptable scope, Object javaObject,
                            Class<?> staticType)
    {
        this(scope, javaObject, staticType, false);
    }

    public NativeJavaObject(Scriptable scope, Object javaObject,
                            Class<?> staticType, boolean isAdapter)
    {
        this.parent = scope;
        this.javaObject = javaObject;
        this.staticType = staticType;
        this.isAdapter = isAdapter;
        initMembers();
    }

    protected void initMembers() {
        Class<?> dynamicType;
        if (javaObject != null) {
            dynamicType = javaObject.getClass();
        } else {
            dynamicType = staticType;
        }
        members = JavaMembers.lookupClass(parent, dynamicType, staticType,
                                          isAdapter);
        fieldAndMethods
            = members.getFieldAndMethodsObjects(this, javaObject, false);
    }

    public boolean has(String name, Scriptable start) {
        return members.has(name, false);
    }

    public boolean has(int index, Scriptable start) {
        return false;
    }

    public Object get(String name, Scriptable start) {
        if (fieldAndMethods != null) {
            Object result = fieldAndMethods.get(name);
            if (result != null) {
                return result;
            }
        }
        // TODO: passing 'this' as the scope is bogus since it has
        //  no parent scope
        return members.get(this, name, javaObject, false);
    }

    public Object get(int index, Scriptable start) {
        throw members.reportMemberNotFound(Integer.toString(index));
    }

    public void put(String name, Scriptable start, Object value) {
        // We could be asked to modify the value of a property in the
        // prototype. Since we can't add a property to a Java object,
        // we modify it in the prototype rather than copy it down.
        if (prototype == null || members.has(name, false))
            members.put(this, name, javaObject, value, false);
        else
            prototype.put(name, prototype, value);
    }

    public void put(int index, Scriptable start, Object value) {
        throw members.reportMemberNotFound(Integer.toString(index));
    }

    public boolean hasInstance(Scriptable value) {
        // This is an instance of a Java class, so always return false
        return false;
    }

    public void delete(String name) {
    }

    public void delete(int index) {
    }

    public Scriptable getPrototype() {
        if (prototype == null && javaObject instanceof String) {
            return TopLevel.getBuiltinPrototype(
                    ScriptableObject.getTopLevelScope(parent),
                    TopLevel.Builtins.String);
        }
        return prototype;
    }

    /**
     * Sets the prototype of the object.
     */
    public void setPrototype(Scriptable m) {
        prototype = m;
    }

    /**
     * Returns the parent (enclosing) scope of the object.
     */
    public Scriptable getParentScope() {
        return parent;
    }

    /**
     * Sets the parent (enclosing) scope of the object.
     */
    public void setParentScope(Scriptable m) {
        parent = m;
    }

    public Object[] getIds() {
        return members.getIds(false);
    }

/**
@deprecated Use {@link Context#getWrapFactory()} together with calling {@link
WrapFactory#wrap(Context, Scriptable, Object, Class)}
*/
    public static Object wrap(Scriptable scope, Object obj, Class<?> staticType) {

        Context cx = Context.getContext();
        return cx.getWrapFactory().wrap(cx, scope, obj, staticType);
    }

    public Object unwrap() {
        return javaObject;
    }

    public String getClassName() {
        return "JavaObject";
    }

    public Object getDefaultValue(Class<?> hint)
    {
        Object value;
        if (hint == null) {
            if (javaObject instanceof Boolean) {
                hint = ScriptRuntime.BooleanClass;
            }
			//增加判断数字类型，否则加法会变成字符串相加
			if (javaObject instanceof Number) {
				hint = ScriptRuntime.NumberClass;
			}
        }
        if (hint == null || hint == ScriptRuntime.StringClass) {
            value = javaObject.toString();
        } else {
            String converterName;
            if (hint == ScriptRuntime.BooleanClass) {
                converterName = "booleanValue";
            } else if (hint == ScriptRuntime.NumberClass) {
                converterName = "doubleValue";
            } else {
                throw Context.reportRuntimeError0("msg.default.value");
            }
            Object converterObject = get(converterName, this);
            if (converterObject instanceof Function) {
                Function f = (Function)converterObject;
                value = f.call(Context.getContext(), f.getParentScope(),
                               this, ScriptRuntime.emptyArgs);
            } else {
                if (hint == ScriptRuntime.NumberClass
                    && javaObject instanceof Boolean)
                {
                    boolean b = ((Boolean)javaObject).booleanValue();
                    value = ScriptRuntime.wrapNumber(b ? 1.0 : 0.0);
                } else {
                    value = javaObject.toString();
                }
            }
        }
        return value;
    }

    /**
     * Determine whether we can/should convert between the given type and the
     * desired one.  This should be superceded by a conversion-cost calculation
     * function, but for now I'll hide behind precedent.
     */
    public static boolean canConvert(Object fromObj, Class<?> to) {
        int weight = getConversionWeight(fromObj, to);

        return (weight < CONVERSION_NONE);
    }

    private static final int JSTYPE_UNDEFINED   = 0; // undefined type
    private static final int JSTYPE_NULL        = 1; // null
    private static final int JSTYPE_BOOLEAN     = 2; // boolean
    private static final int JSTYPE_NUMBER      = 3; // number
    private static final int JSTYPE_STRING      = 4; // string
    private static final int JSTYPE_JAVA_CLASS  = 5; // JavaClass
    private static final int JSTYPE_JAVA_OBJECT = 6; // JavaObject
    private static final int JSTYPE_JAVA_ARRAY  = 7; // JavaArray
    private static final int JSTYPE_OBJECT      = 8; // Scriptable

    static final byte CONVERSION_TRIVIAL      = 1;
    static final byte CONVERSION_NONTRIVIAL   = 0;
    static final byte CONVERSION_NONE         = 99;

    /**
     * Derive a ranking based on how "natural" the conversion is.
     * The special value CONVERSION_NONE means no conversion is possible,
     * and CONVERSION_NONTRIVIAL signals that more type conformance testing
     * is required.
     * Based on
     * <a href="http://www.mozilla.org/js/liveconnect/lc3_method_overloading.html">
     * "preferred method conversions" from Live Connect 3</a>
     */
    static int getConversionWeight(Object fromObj, Class<?> to) {
        int fromCode = getJSTypeCode(fromObj);

        switch (fromCode) {

        case JSTYPE_UNDEFINED:
            if (to == ScriptRuntime.StringClass || to == ScriptRuntime.ObjectClass) {
				return 1;
			}
			break;

		case JSTYPE_NULL:
			if (!to.isPrimitive()) {
				return 1;
			}
			break;

		case JSTYPE_BOOLEAN:
			// "boolean" is #1
			if (to == Boolean.TYPE) {
				return 1;
			} else if (to == ScriptRuntime.BooleanClass) {
				return 2;
			} else if (to == ScriptRuntime.ObjectClass) {
				return 3;
			} else if (to == ScriptRuntime.StringClass) {
				return 4;
			}
			break;

		case JSTYPE_NUMBER:
			if (to.isPrimitive()) {
				if (to == Double.TYPE) {
					return 1;
				} else if (to != Boolean.TYPE) {
					return 1 + getSizeRank(to);
				}
			} else {
				if (to == ScriptRuntime.StringClass) {
					// native numbers are #1-8
					return 9;
				} else if (to == ScriptRuntime.ObjectClass) {
					return 10;
				} else if (ScriptRuntime.NumberClass.isAssignableFrom(to)) {
					// "double" is #1
					return 2;
				}
			}
			break;

		case JSTYPE_STRING:
			if (to == ScriptRuntime.StringClass) {
				return 1;
			} else if (to.isInstance(fromObj)) {
				return 2;
			} else if (to.isPrimitive()) {
				if (to == Character.TYPE) {
					return 3;
				} else if (to != Boolean.TYPE) {
					return 4;
				}
			}
			break;

		case JSTYPE_JAVA_CLASS:
			if (to == ScriptRuntime.ClassClass) {
				return 1;
			} else if (to == ScriptRuntime.ObjectClass) {
				return 3;
			} else if (to == ScriptRuntime.StringClass) {
				return 4;
			}
			break;

		case JSTYPE_JAVA_OBJECT:
		case JSTYPE_JAVA_ARRAY:
			Object javaObj = fromObj;
			if (javaObj instanceof Wrapper) {
				javaObj = ((Wrapper) javaObj).unwrap();
			}
			if (to.isInstance(javaObj)) {
				return CONVERSION_NONTRIVIAL;
			}
			if (to == ScriptRuntime.StringClass) {
				return 2;
			} else if (to.isPrimitive() && to != Boolean.TYPE) {
				return (fromCode == JSTYPE_JAVA_ARRAY) ? CONVERSION_NONE
						: 2 + getSizeRank(to);
			}
			break;

		case JSTYPE_OBJECT:
			// Other objects takes #1-#3 spots
			if (to != ScriptRuntime.ObjectClass && to.isInstance(fromObj)) {
				// No conversion required, but don't apply for java.lang.Object
				return 1;
			}
			if (to.isArray()) {
				if (fromObj instanceof NativeArray) {
					// This is a native array conversion to a java array
					// Array conversions are all equal, and preferable to object
					// and string conversion, per LC3.
					return 2;
				}
			} else if (to == ScriptRuntime.ObjectClass) {
				return 3;
			} else if (to == ScriptRuntime.StringClass) {
				return 4;
			} else if (to == ScriptRuntime.DateClass) {
				if (fromObj instanceof NativeDate) {
					// This is a native date to java date conversion
					return 1;
				}
			} else if (to.isInterface()) {
				if (fromObj instanceof NativeObject
						|| fromObj instanceof NativeFunction) {
					// See comments in createInterfaceAdapter
					return 1;
				}
				return 12;
			} else if (to.isPrimitive() && to != Boolean.TYPE) {
				return 4 + getSizeRank(to);
			}
			break;
		}

		return CONVERSION_NONE;
	}

	static int getSizeRank(Class<?> aType) {
		if (aType == Double.TYPE) {
			return 1;
		} else if (aType == Float.TYPE) {
			return 2;
		} else if (aType == Long.TYPE) {
			return 3;
		} else if (aType == Integer.TYPE) {
			return 4;
		} else if (aType == Short.TYPE) {
			return 5;
		} else if (aType == Character.TYPE) {
			return 6;
		} else if (aType == Byte.TYPE) {
			return 7;
		} else if (aType == Boolean.TYPE) {
			return CONVERSION_NONE;
		} else {
			return 8;
		}
	}

	private static int getJSTypeCode(Object value) {
		if (value == null) {
			return JSTYPE_NULL;
		} else if (value == Undefined.instance) {
			return JSTYPE_UNDEFINED;
		} else if (value instanceof CharSequence) {
			return JSTYPE_STRING;
		} else if (value instanceof Number) {
			return JSTYPE_NUMBER;
		} else if (value instanceof Boolean) {
			return JSTYPE_BOOLEAN;
		} else if (value instanceof Scriptable) {
			if (value instanceof NativeJavaClass) {
				return JSTYPE_JAVA_CLASS;
			} else if (value instanceof NativeJavaArray) {
				return JSTYPE_JAVA_ARRAY;
			} else if (value instanceof Wrapper) {
				return JSTYPE_JAVA_OBJECT;
			} else {
				return JSTYPE_OBJECT;
			}
		} else if (value instanceof Class) {
			return JSTYPE_JAVA_CLASS;
		} else {
			Class<?> valueClass = value.getClass();
			if (valueClass.isArray()) {
				return JSTYPE_JAVA_ARRAY;
			} else {
				return JSTYPE_JAVA_OBJECT;
			}
		}
	}

	/**
	 * Not intended for public use. Callers should use the public API
	 * Context.toType.
	 * 
	 * @deprecated as of 1.5 Release 4
	 * @see org.mozilla.javascript.Context#jsToJava(Object, Class)
	 */
	public static Object coerceType(Class<?> type, Object value) {
		return coerceTypeImpl(type, value);
	}

	/**
	 * Type-munging for field setting and method invocation. Conforms to LC3
	 * specification
	 */
	static Object coerceTypeImpl(Class<?> type, Object value) {
		if (value != null && value.getClass() == type) {
			return value;
		}

		switch (getJSTypeCode(value)) {

		case JSTYPE_NULL:
			// raise error if type.isPrimitive()
			if (type.isPrimitive()) {
				reportConversionError(value, type);
			}
			return null;

		case JSTYPE_UNDEFINED:
			if (type == ScriptRuntime.StringClass
					|| type == ScriptRuntime.ObjectClass) {
				return "undefined";
			} else {
				reportConversionError("undefined", type);
			}
			break;

		case JSTYPE_BOOLEAN:
			// Under LC3, only JS Booleans can be coerced into a Boolean value
			if (type == Boolean.TYPE || type == ScriptRuntime.BooleanClass
					|| type == ScriptRuntime.ObjectClass) {
				return value;
			} else if (type == ScriptRuntime.StringClass) {
				return value.toString();
			} else {
				reportConversionError(value, type);
			}
			break;

		case JSTYPE_NUMBER:
			if (type == ScriptRuntime.StringClass) {
				return ScriptRuntime.toString(value);
			} else if (type == ScriptRuntime.ObjectClass) {
				//调整数值类型为对应的数据值，不需要全部转为double
				if (value != null)
					return coerceToNumber(value.getClass(), value);
				else
					return coerceToNumber(Double.TYPE, value);
			} else if ((type.isPrimitive() && type != Boolean.TYPE)
					|| ScriptRuntime.NumberClass.isAssignableFrom(type)) {
				return coerceToNumber(type, value);
			} else {
				reportConversionError(value, type);
			}
			break;

		case JSTYPE_STRING:
			if (type == ScriptRuntime.StringClass || type.isInstance(value)) {
				return value.toString();
			} else if (type == Character.TYPE
					|| type == ScriptRuntime.CharacterClass) {
				// Special case for converting a single char string to a
				// character
				// Placed here because it applies *only* to JS strings,
				// not other JS objects converted to strings
				if (((CharSequence) value).length() == 1) {
					return Character.valueOf(((CharSequence) value).charAt(0));
				} else {
					return coerceToNumber(type, value);
				}
			} else if ((type.isPrimitive() && type != Boolean.TYPE)
					|| ScriptRuntime.NumberClass.isAssignableFrom(type)) {
				return coerceToNumber(type, value);
			} else {
				reportConversionError(value, type);
			}
			break;

		case JSTYPE_JAVA_CLASS:
			if (value instanceof Wrapper) {
				value = ((Wrapper) value).unwrap();
			}

			if (type == ScriptRuntime.ClassClass
					|| type == ScriptRuntime.ObjectClass) {
				return value;
			} else if (type == ScriptRuntime.StringClass) {
				return value.toString();
			} else {
				reportConversionError(value, type);
			}
			break;

		case JSTYPE_JAVA_OBJECT:
		case JSTYPE_JAVA_ARRAY:
			if (value instanceof Wrapper) {
				value = ((Wrapper) value).unwrap();
			}
			if (type.isPrimitive()) {
				if (type == Boolean.TYPE) {
					if (value instanceof Boolean)
						return value;
					else
						reportConversionError(value, type);
				}
				return coerceToNumber(type, value);
			} else {
				if (type == ScriptRuntime.StringClass) {
					return value.toString();
				} else {
					if (type.isInstance(value)) {
						return value;
					} else {
						reportConversionError(value, type);
					}
				}
			}
			break;

		case JSTYPE_OBJECT:
			if (type == ScriptRuntime.StringClass) {
				return ScriptRuntime.toString(value);
			} else if (type.isPrimitive()) {
				if (type == Boolean.TYPE) {
					reportConversionError(value, type);
				}
				return coerceToNumber(type, value);
			} else if (type.isInstance(value)) {
				return value;
			} else if (type == ScriptRuntime.DateClass
					&& value instanceof NativeDate) {
				double time = ((NativeDate) value).getJSTimeValue();
				// XXX: This will replace NaN by 0
				return new Date((long) time);
			} else if (type.isArray() && value instanceof NativeArray) {
				// Make a new java array, and coerce the JS array components
				// to the target (component) type.
				NativeArray array = (NativeArray) value;
				long length = array.getLength();
				Class<?> arrayType = type.getComponentType();
				Object Result = Array.newInstance(arrayType, (int) length);
				for (int i = 0; i < length; ++i) {
					try {
						Array.set(Result, i,
								coerceTypeImpl(arrayType, array.get(i, array)));
					} catch (EvaluatorException ee) {
						reportConversionError(value, type);
					}
				}

				return Result;
			} else if (value instanceof Wrapper) {
				value = ((Wrapper) value).unwrap();
				if (type.isInstance(value))
					return value;
				reportConversionError(value, type);
			} else if (type.isInterface()
					&& (value instanceof NativeObject || value instanceof NativeFunction)) {
				// Try to use function/object as implementation of Java
				// interface.
				return createInterfaceAdapter(type, (ScriptableObject) value);
			} else {
				reportConversionError(value, type);
			}
			break;
		}

		return value;
	}

	protected static Object createInterfaceAdapter(Class<?> type,
			ScriptableObject so) {
		// XXX: Currently only instances of ScriptableObject are
		// supported since the resulting interface proxies should
		// be reused next time conversion is made and generic
		// Callable has no storage for it. Weak references can
		// address it but for now use this restriction.

		Object key = Kit.makeHashKeyFromPair(COERCED_INTERFACE_KEY, type);
		Object old = so.getAssociatedValue(key);
		if (old != null) {
			// Function was already wrapped
			return old;
		}
		Context cx = Context.getContext();
		Object glue = InterfaceAdapter.create(cx, type, so);
		// Store for later retrieval
		glue = so.associateValue(key, glue);
		return glue;
	}

	private static Object coerceToNumber(Class<?> type, Object value) {
		Class<?> valueClass = value.getClass();

		// Character
		if (type == Character.TYPE || type == ScriptRuntime.CharacterClass) {
			if (valueClass == ScriptRuntime.CharacterClass) {
				return value;
			}
			return Character.valueOf((char) toInteger(value,
					ScriptRuntime.CharacterClass, Character.MIN_VALUE,
					Character.MAX_VALUE));
		}

		// Double, Float
		if (type == ScriptRuntime.ObjectClass
				|| type == ScriptRuntime.DoubleClass || type == Double.TYPE) {
			return valueClass == ScriptRuntime.DoubleClass ? value
					: new Double(toDouble(value));
		}

		if (type == ScriptRuntime.FloatClass || type == Float.TYPE) {
			if (valueClass == ScriptRuntime.FloatClass) {
				return value;
			} else {
				double number = toDouble(value);
				if (Double.isInfinite(number) || Double.isNaN(number)
						|| number == 0.0) {
					return new Float((float) number);
				} else {
					double absNumber = Math.abs(number);
					if (absNumber < Float.MIN_VALUE) {
						return new Float((number > 0.0) ? +0.0 : -0.0);
					} else if (absNumber > Float.MAX_VALUE) {
						return new Float(
								(number > 0.0) ? Float.POSITIVE_INFINITY
										: Float.NEGATIVE_INFINITY);
					} else {
						return new Float((float) number);
					}
				}
			}
		}

		// Integer, Long, Short, Byte
		if (type == ScriptRuntime.IntegerClass || type == Integer.TYPE) {
			if (valueClass == ScriptRuntime.IntegerClass) {
				return value;
			} else {
				return Integer.valueOf((int) toInteger(value,
						ScriptRuntime.IntegerClass, Integer.MIN_VALUE,
						Integer.MAX_VALUE));
			}
		}

		if (type == ScriptRuntime.LongClass || type == Long.TYPE) {
			if (valueClass == ScriptRuntime.LongClass) {
				return value;
			} else {
				/*
				 * Long values cannot be expressed exactly in doubles. We thus
				 * use the largest and smallest double value that has a value
				 * expressible as a long value. We build these numerical values
				 * from their hexidecimal representations to avoid any problems
				 * caused by attempting to parse a decimal representation.
				 */
				final double max = Double.longBitsToDouble(0x43dfffffffffffffL);
				final double min = Double.longBitsToDouble(0xc3e0000000000000L);
				return Long.valueOf(toInteger(value, ScriptRuntime.LongClass,
						min, max));
			}
		}

		if (type == ScriptRuntime.ShortClass || type == Short.TYPE) {
			if (valueClass == ScriptRuntime.ShortClass) {
				return value;
			} else {
				return Short.valueOf((short) toInteger(value,
						ScriptRuntime.ShortClass, Short.MIN_VALUE,
						Short.MAX_VALUE));
			}
		}

		if (type == ScriptRuntime.ByteClass || type == Byte.TYPE) {
			if (valueClass == ScriptRuntime.ByteClass) {
				return value;
			} else {
				return Byte
						.valueOf((byte) toInteger(value,
								ScriptRuntime.ByteClass, Byte.MIN_VALUE,
								Byte.MAX_VALUE));
			}
		}

		return new Double(toDouble(value));
	}

	private static double toDouble(Object value) {
		if (value instanceof Number) {
			return ((Number) value).doubleValue();
		} else if (value instanceof String) {
			return ScriptRuntime.toNumber((String) value);
		} else if (value instanceof Scriptable) {
			if (value instanceof Wrapper) {
				// XXX: optimize tail-recursion?
				return toDouble(((Wrapper) value).unwrap());
			} else {
				return ScriptRuntime.toNumber(value);
			}
		} else {
			Method meth;
			try {
				meth = value.getClass()
						.getMethod("doubleValue", (Class[]) null);
			} catch (NoSuchMethodException e) {
				meth = null;
			} catch (SecurityException e) {
				meth = null;
			}
			if (meth != null) {
				try {
					return ((Number) meth.invoke(value, (Object[]) null))
							.doubleValue();
				} catch (IllegalAccessException e) {
					// XXX: ignore, or error message?
					reportConversionError(value, Double.TYPE);
				} catch (InvocationTargetException e) {
					// XXX: ignore, or error message?
					reportConversionError(value, Double.TYPE);
				}
			}
			return ScriptRuntime.toNumber(value.toString());
		}
	}

	private static long toInteger(Object value, Class<?> type, double min,
			double max) {
		double d = toDouble(value);

		if (Double.isInfinite(d) || Double.isNaN(d)) {
			// Convert to string first, for more readable message
			reportConversionError(ScriptRuntime.toString(value), type);
		}

		if (d > 0.0) {
			d = Math.floor(d);
		} else {
			d = Math.ceil(d);
		}

		if (d < min || d > max) {
			// Convert to string first, for more readable message
			reportConversionError(ScriptRuntime.toString(value), type);
		}
		return (long) d;
	}

	static void reportConversionError(Object value, Class<?> type) {
		// It uses String.valueOf(value), not value.toString() since
		// value can be null, bug 282447.
		throw Context.reportRuntimeError2("msg.conversion.not.allowed",
				String.valueOf(value), JavaMembers.javaSignature(type));
	}

	private void writeObject(ObjectOutputStream out) throws IOException {
		out.defaultWriteObject();

		out.writeBoolean(isAdapter);
		if (isAdapter) {
			if (adapter_writeAdapterObject == null) {
				throw new IOException();
			}
			Object[] args = { javaObject, out };
			try {
				adapter_writeAdapterObject.invoke(null, args);
			} catch (Exception ex) {
				throw new IOException();
			}
		} else {
			out.writeObject(javaObject);
		}

		if (staticType != null) {
			out.writeObject(staticType.getClass().getName());
		} else {
			out.writeObject(null);
		}
	}

	private void readObject(ObjectInputStream in) throws IOException,
			ClassNotFoundException {
		in.defaultReadObject();

		isAdapter = in.readBoolean();
		if (isAdapter) {
			if (adapter_readAdapterObject == null)
				throw new ClassNotFoundException();
			Object[] args = { this, in };
			try {
				javaObject = adapter_readAdapterObject.invoke(null, args);
			} catch (Exception ex) {
				throw new IOException();
			}
		} else {
			javaObject = in.readObject();
		}

		String className = (String) in.readObject();
		if (className != null) {
			staticType = Class.forName(className);
		} else {
			staticType = null;
		}

		initMembers();
	}

	/**
	 * The prototype of this object.
	 */
	protected Scriptable prototype;

	/**
	 * The parent scope of this object.
	 */
	protected Scriptable parent;

	protected transient Object javaObject;

	protected transient Class<?> staticType;
	protected transient JavaMembers members;
	private transient Map<String, FieldAndMethods> fieldAndMethods;
	protected transient boolean isAdapter;

	private static final Object COERCED_INTERFACE_KEY = "Coerced Interface";
	private static Method adapter_writeAdapterObject;
	private static Method adapter_readAdapterObject;

	static {
		// Reflection in java is verbose
		Class<?>[] sig2 = new Class[2];
		Class<?> cl = Kit.classOrNull("org.mozilla.javascript.JavaAdapter");
		if (cl != null) {
			try {
				sig2[0] = ScriptRuntime.ObjectClass;
				sig2[1] = Kit.classOrNull("java.io.ObjectOutputStream");
				adapter_writeAdapterObject = cl.getMethod("writeAdapterObject",
						sig2);

				sig2[0] = ScriptRuntime.ScriptableClass;
				sig2[1] = Kit.classOrNull("java.io.ObjectInputStream");
				adapter_readAdapterObject = cl.getMethod("readAdapterObject",
						sig2);

			} catch (NoSuchMethodException e) {
				adapter_writeAdapterObject = null;
				adapter_readAdapterObject = null;
			}
		}
	}

}
